#include <tinx/io.h>
#include <tinx/lock.h>
#include <tinx/interrupt.h>
#include <tinx/task.h>
#include <tinx/device.h>
#include <tinx/debug.h>
#include <tinx/vsprintf.h>
#include <tinx/fifo.h>
#include <tinx/string.h>
#include <tinx/assert.h>

#define LOGK(str, args...) DEBUGK(str, ##args)

#define COM1_IOBASE 0x3F8 // 串口 1 基地址
#define COM2_IOBASE 0x2F8 // 串口 2 基地址
#define COM1_VECTOR 0x24

#define COM_DATA 0          // 数据寄存器
#define COM_INTR_ENABLE 1   // 中断允许
#define COM_BAUD_LSB 0      // 波特率低字节
#define COM_BAUD_MSB 1      // 波特率高字节
#define COM_INTR_IDENTIFY 2 // 中断识别
#define COM_LINE_CONTROL 3  // 线控制
#define COM_MODEM_CONTROL 4 // 调制解调器控制
#define COM_LINE_STATUS 5   // 线状态
#define COM_MODEM_STATUS 6  // 调制解调器状态

// 线状态
#define LSR_DR 0x1
#define LSR_OE 0x2
#define LSR_PE 0x4
#define LSR_FE 0x8
#define LSR_BI 0x10
#define LSR_THRE 0x20
#define LSR_TEMT 0x40
#define LSR_IE 0x80

#define NR_SERIAL 2

#define BUF_LEN 64

typedef struct serial_t
{
    char name[8];
    u16 iobase;
    fifo_t rfifo;
    char rbuf[BUF_LEN];
    lock_t rlock;
    task_t *rwaiter;
    lock_t wlock;
} serial_t;

static serial_t serials[NR_SERIAL];

void recv_data(serial_t *serial)
{
    char ch = inb(serial->iobase);
    if (ch == '\r') // 特殊处理，回车键直接换行
    {
        ch = '\n';
    }
    fifo_put(&serial->rfifo, ch);
    if (serial->rwaiter != NULL)
    {
        task_unblock(serial->rwaiter, EOK);
        serial->rwaiter = NULL;
    }
}

// 中断处理函数
void serial_handler(u8 vector)
{
    u32 irq = vector - 0x20;
    assert(irq == IRQ_SERIAL_1 || irq == IRQ_SERIAL_2);
    send_eoi(vector);

    serial_t *serial = &serials[irq - IRQ_SERIAL_1];
    u8 state = inb(serial->iobase + COM_LINE_STATUS);

    if (state & LSR_DR) // 数据可读
    {
        recv_data(serial);
    }
}

int serial_read(serial_t *serial, char *buf, u32 count)
{
    lock_acquire(&serial->rlock);
    int nr = 0;
    while (nr < count)
    {
        while (fifo_empty(&serial->rfifo))
        {
            assert(serial->rwaiter == NULL);
            serial->rwaiter = running_task;
            task_block(serial->rwaiter, TASK_BLOCKED);
        }
        buf[nr++] = fifo_get(&serial->rfifo);
    }
    lock_release(&serial->rlock);
    return nr;
}

int serial_write(serial_t *serial, char *buf, int count)
{
    lock_acquire(&serial->wlock);
    for (size_t i = 0; i < count; i++)
    {
        if (inb(serial->iobase + COM_LINE_STATUS) & LSR_THRE)
        {
            outb(serial->iobase, buf[i]);
        }
    }
    lock_release(&serial->wlock);
    return count;
}

void serial_init()
{
    for (size_t i = 0; i < 2; i++)
    {
        serial_t *serial = &serials[i];
        memset(serial, 0, sizeof(serial_t));

        serial->iobase = i ? COM2_IOBASE : COM1_IOBASE;

        fifo_init(&serial->rfifo, serial->rbuf, BUF_LEN);
        lock_init(&serial->rlock);
        lock_init(&serial->wlock);
        u16  iobase = serial->iobase;

        outb(iobase + COM_INTR_ENABLE, 0);

        // 置位 DLAB, 设置波特率因子
        outb(iobase + COM_LINE_CONTROL, 0b10000000);
        outb(iobase + COM_BAUD_LSB, 0x30);
        outb(iobase + COM_BAUD_MSB, 0x00);

        // 复位 DLAB, 设置数据宽度
        outb(iobase + COM_LINE_CONTROL, 0x03);

        // 初始化 FIFO
        outb(iobase + COM_INTR_IDENTIFY, 0xc7);

        // 测试串口是否可用
        outb(iobase + COM_MODEM_CONTROL, 0b11011);

        outb(iobase, 0x5A);
        if (inb(iobase) != 0x5A)
            continue; // 串口不可用就跳过

        outb(iobase + COM_MODEM_CONTROL, 0b1011);
        outb(iobase + COM_INTR_ENABLE, 0x0f);

        sprintf(serial->name, "com%d", iobase == COM1_IOBASE ? 1 : 2);
        device_create(
            DEV_TYPE_CHAR, DEV_FLAG_SERIAL, -1, serial->name,
            serial, serial_write, serial_read, NULL);
    }

    LOGK("serial init ...\n");
}